Completed
Push — dev ( 2c9027...763fa0 )
by Fike
27s
created

Future.js ➔ ... ➔ this.isFulfilled   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 1
rs 10
1
var Race = require('./Race').Race
2
3
/**
4
 * @callback Future~resolver
5
 *
6
 * @param {Function} resolve
7
 * @param {Function} reject
8
 */
9
10
/**
11
 * @enum
12
 * @readonly
13
 */
14
var Status = {
15
  Pending: 0,
16
  Resolving: 1,
17
  Rejected: 2,
18
  Fulfilled: 3
19
}
20
21
Status.name = function (value) {
22
  return Object.keys(Status).reduce(function (carrier, key) {
23
    return carrier || (Status[key] === value ? key : null)
24
  }, null)
25
}
26
27
/**
28
 * This is a very standard promise interface implementation, but with ability
29
 * to be externally completed or cancelled by `#resolve()` and `#reject()`
30
 * methods.
31
 *
32
 * It is named after Java's CompletableFuture.
33
 *
34
 * **NB:** this implementation doesn't account for cyclic references (which are
35
 * tremendously easy to implement).
36
 *
37
 * @class
38
 * @template T
39
 *
40
 * @param {Future~resolver} [resolver] Standard promise resolver
41
 */
42
function Future (resolver) {
43
  var self = this
44
  var status = Status.Pending
45
  var identity
46
  var propagation = null
47
  var queue = []
48
49
  this.getValue = function () { return identity }
50
51
  this.getStatus = function () { return status }
52
53
  this.hasStatus = function (s) { return status === s }
54
55
  this.isPending = function () { return self.hasStatus(Status.Pending) }
56
  this.isFulfilled = function () { return self.hasStatus(Status.Fulfilled) }
57
  this.isRejected = function () { return self.hasStatus(Status.Rejected) }
58
  this.isResolved = function () {
59
    return status === Status.Fulfilled || status === Status.Rejected
60
  }
61
62
  function schedulePropagation () {
63
    // if already scheduled
64
    if (propagation) { return }
65
    // @see https://promisesaplus.com/#point-34
66
    // setTimeout is used because process.nextTick is not something one mocks
67
    propagation = setTimeout(function () {
68
      propagation = null
69
      propagate()
70
    }, 0)
71
  }
72
73
  function propagate () {
74
    if (!self.isResolved()) { return }
75
    var staged = queue
76
    queue = []
77
    staged.forEach(function (callback) {
78
      callback(status, identity)
79
    })
80
  }
81
82
  function setIdentity (nextStatus, value) {
83
    if (self.isResolved()) { return }
84
    status = nextStatus
85
    identity = value
86
    schedulePropagation()
87
  }
88
89
  var fulfill = setIdentity.bind(self, Status.Fulfilled)
1 ignored issue
show
Unused Code introduced by
The call to bind does not seem necessary since the function setIdentity declared on line 82 does not use this.
Loading history...
90
  var reject = setIdentity.bind(self, Status.Rejected)
1 ignored issue
show
Unused Code introduced by
The call to bind does not seem necessary since the function setIdentity declared on line 82 does not use this.
Loading history...
91
92
  function resolve (nextStatus, value) {
93
    if (!self.isPending()) { return self }
94
    extendedResolutionProcedure(nextStatus, value)
95
    return self
96
  }
97
98
  /**
99
   * Resolves (fulfills) current instance with provided value
100
   *
101
   * @param {T} [value]
102
   * @returns {Future.<T>} Current instance
103
   */
104
  this.fulfill = resolve.bind(this, Status.Fulfilled)
1 ignored issue
show
Unused Code introduced by
The call to bind does not seem necessary since the function resolve declared on line 92 does not use this.
Loading history...
105
106
  this.resolve = this.fulfill
107
108
  /**
109
   * Rejects current instance with provided value
110
   *
111
   * @param {T} [value]
112
   * @returns {Future.<T>} Current instance
113
   */
114
  this.reject = resolve.bind(this, Status.Rejected)
1 ignored issue
show
Unused Code introduced by
The call to bind does not seem necessary since the function resolve declared on line 92 does not use this.
Loading history...
115
116
  if (resolver) { resolver(this.resolve, this.reject) }
117
118
  /**
119
   * @param x
120
   */
121
  function promiseResolutionProcedure (x) {
122
    if (self.isResolved()) { return }
123
    status = Status.Resolving
124
    if (x === self) {
125
      var message = 'Can\'t resolve promise with itself'
126
      return setIdentity(Status.Rejected, new TypeError(message))
127
    }
128
    if (x instanceof Future && x.isResolved()) {
129
      return extendedResolutionProcedure(x.getStatus(), x.getValue())
130
    }
131
    if (!x || (typeof x !== 'function' && typeof x !== 'object')) {
132
      return fulfill(x)
133
    }
134
    var race = new Race(1)
135
    var resolvePromise = race.racer(promiseResolutionProcedure)
136
    var rejectPromise = race.racer(reject)
137
    try {
138
      var then = x.then
139
      if (typeof then !== 'function') { return fulfill(x) }
140
      then.call(x, resolvePromise, rejectPromise)
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
141
    } catch (e) {
142
      rejectPromise(e)
1 ignored issue
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
143
    }
144
  }
145
146
  function extendedResolutionProcedure (status, x) {
147
    status === Status.Rejected ? reject(x) : promiseResolutionProcedure(x)
148
    return self
149
  }
150
151
  /**
152
   * Standard then-implementation
153
   *
154
   * @param {Function} [onFulfill]
155
   * @param {Function} [onReject]
156
   * @returns {Future.<T>}
157
   */
158
  this.then = function (onFulfill, onReject) {
159
    if (typeof onFulfill !== 'function') { onFulfill = null }
160
    if (typeof onReject !== 'function') { onReject = null }
161
    if (!onFulfill && !onReject) { return self }
162
    onReject = onReject || function (error) { throw error }
163
    onFulfill = onFulfill || function (value) { return value }
164
    var target = new Future()
165
    var callback = function (status, value) {
166
      var handler = status === Status.Fulfilled ? onFulfill : onReject
167
      try {
168
        target.resolve(handler(value))
169
      } catch (e) {
170
        target.reject(e)
171
      }
172
    }
173
    queue.push(callback)
174
    schedulePropagation()
175
    return target
176
  }
177
178
  /**
179
   * Attaches current promise to provided thenable, accepting it's eventual
180
   * outcome.
181
   *
182
   * @param {Thenable} promise
183
   *
184
   * @return {Future}
185
   */
186
  this.wrap = function (promise) {
187
    promise.then(function (value) {
188
      self.fulfill(value)
189
    }, function (e) {
190
      self.reject(e)
191
    })
192
    return self
193
  }
194
195
  this.toString = function () {
196
    var state = Status.name(status)
197
    return 'Future <' + state + (identity ? ':' + identity : '') + '>'
198
  }
199
}
200
201
/**
202
 * Returns promise that awaits all passed promises.
203
 *
204
 * @param {Array.<Promise.<*>|Thenable<*>>} promises
205
 * @returns {Future.<*>}
206
 */
207
Future.all = function (promises) {
208
  return Future.wrap(Promise.all(promises))
209
}
210
211
/**
212
 * Returns result of first resolved promise, be it fulfillment or rejection
213
 *
214
 * @param {Array.<Promise.<*>|Thenable<*>>} promises
215
 * @returns {Future.<*>}
216
 */
217
Future.race = function (promises) {
218
  return Future.wrap(Promise.race(promises))
219
}
220
221
/**
222
 * Returns resolved promise.
223
 *
224
 * @param {*} [value]
225
 * @returns {Future.<*>}
226
 */
227
Future.resolve = function (value) {
228
  return new Future().resolve(value)
229
}
230
231
/**
232
 * Returns rejected promise.
233
 *
234
 * @param {*} [value]
235
 * @returns {Future.<*>}
236
 */
237
Future.reject = function (value) {
238
  return new Future().reject(value)
239
}
240
241
/**
242
 * Wraps given promise in a Future, giving user code an option
243
 * to reject/resolve it.
244
 *
245
 * @param {Promise.<*>|Thenable.<*>} promise
246
 * @returns {Future.<*>}
247
 */
248
Future.wrap = function (promise) {
249
  return new Future().wrap(promise)
250
}
251
252
Future.Status = Status
253
254
module.exports = {
255
  Future: Future
256
}
257